#!/bin/sh

# Copyright (c) 2001-2004 Todd T. Fries <todd@OpenBSD.org>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

[ "${SYSCONFDIR}" ] || SYSCONFDIR=/etc
[ "${TRUEPREFIX}" ] || TRUEPREFIX=/usr/local

afsp=${TRUEPREFIX}/libexec/openafs
PATH=${TRUEPREFIX}/sbin:${TRUEPREFIX}/bin:$PATH
DIR=`mktemp -d /tmp/_openafs.XXXXXXXXXX` || exit 1
trap 'rm -rf $DIR; exit 1' 0 1 2 3 13 15
OUTPUT=$DIR/_1
defuser="$USER"
if [ "$defuser" = "root" ]; then
	if [ "$SUDO_USER" ]; then
		defuser="$SUDO_USER"
	else
		defuser="todd"
	fi
fi

# (borrowed from install.sub)
# Ask for user input.
#
#    $1    = the question to ask the user
#    $2    = the default answer
#
# Save the user input (or the default) in $resp.
#
# Allow the user to escape to shells ('!') or execute commands
# ('!foo') before entering the input.
ask() {
	local _question=$1 _default=$2

	set -o noglob
	while :; do
	        echo -n "$_question "
	        [[ -z $_default ]] || echo -n "[$_default] "
	        read resp
	        case $resp in
	        !)      echo "Type 'exit' to return to install."
	                sh
	                ;;
	        !*)     eval ${resp#?}
	                ;;
	        *)      : ${resp:=$_default}
	                break
	                ;;
	        esac
	done
	set +o noglob
}

# Ask for user input until a non-empty reply is entered.
#
#    $1    = the question to ask the user
#    $2    = the default answer
#
# Save the user input (or the default) in $resp.
ask_until() {
	resp=
	while [[ -z $resp ]] ; do
	        ask "$1" "$2"
	done
}

# Ask the user for a y or n, and insist on 'y', 'yes', 'n' or 'no'.
#
#    $1    = the question to ask the user
#    $2    = the default answer (assumed to be 'n' if empty).
#
# Return 'y' or 'n' in $resp.
ask_yn() {
	local _q=$1 _a=${2:-no} _resp
	typeset -l _resp

	while :; do
	        ask "$_q" "$_a"
	        _resp=$resp
	        case $_resp in
	        y|yes)  resp=y ; return ;;
	        n|no)   resp=n ; return ;;
	        esac
	done
}


# Logging routine
# 
#    $1    = -c or 1st arg
#    $2    = ...
#
# log all arguments
sc=0
log() {
	local prompt=":"
	if [ "$1" = "-c" ]; then
		prompt="#"
		shift
	else
		let sc=sc+1
	fi
	dfmt="%H:%M:%S"
	printf "%s %02d%s " "$(date +"${dfmt}")" $sc "$prompt"
	# use echo, because printf(1) treats varargs as one arg per line, ugh
	echo "$@"
}

# Create a principal in kerberos.
#
#    $1    = principal
#    $2    = extra arg..
#
# Delete the principal first before re-adding it to make sure proper
# attributes exist.
kadd() {
	local principal=$1
	log creating principal: $principal
	shift
	kadmin del $principal > /dev/null 2>&1
	log -c kadmin add "$@" $principal
	kadmin add \
		--{pw-,}expiration-time=never \
		--max-ticket-life="1 month" \
		--max-renewable-life="2 months" \
		--attributes="" \
		"$@" $principal
}

# Re-try a command until success.
#
#    $@    = full command to try
#
retry() {
	local try=1
	log -c "$@"
	while ! $@
	do
		let try=try+1
		log -c "$@ : try $try"
		sleep 2
	done
}

# Make an afs volume.
#
#    $1    = volume name
#    $2    = volume mount point
#
# Any user can read volumes created here.
mkvol() {
	local vol=$1 mnt=$2
	log "Creating afs volume $1 to be mounted at $mnt"
	retry vos create $h /vicepa $vol
	retry fs mkm $mnt $vol
	retry fs sa $mnt system:anyuser rl
}

cat <<__EOT

===========================================================================
Welcome to the OpenAFS server1 setup script!

This script will assist you in setting up your first afs server.

It will use OpenAFS for the AFS server, but arla's afsd that comes with
OpenBSD for the AFS client.

It will use heimdal KerberosV that comes with OpenBSD.

It presumes you have previously successfully setup a KerberosV realm,
you have the password to an administrative principal in the KerberosV realm,
you are running it as root, and you have created at least one partition
for OpenAFS to use for data storage.  Partitions should be mounted under
/vicepa, /vicepb, /vicepc, etc.

===========================================================================
__EOT

#
# Sanity checks
#

# Require root.
if [[ `/usr/bin/whoami` != "root" ]]
then
	echo "Please run this script as root.  Thanks."
	exit
fi
if [[ `mount|grep " /vicepa "` = "" ]]
then
	echo "Could not find any filesystem mounted at /vicepa"
	echo "Without this OpenAFS will not function."
	echo "Please mount a partition under /vicepa"
	echo "A /vicepa directory will not work as"
	echo "OpenAFS"
	exit
fi

#
# Setup site specific variables
#

# Ask user for variables if not passed on the command line.
if ! [ "$6" ]
then
	cat <<__EOT

The hostname for this afs server should resolve in dns but
definitely reside in /etc/hosts. e.g. afs0.example.com.
__EOT
	while :; do
		ask_until "System hostname?" "$(hostname)"
		h=${resp}
		if [[ `grep $h /etc/hosts` = "" ]]
		then
			echo "Could not find /etc/hosts entry for $h."
			continue
		fi
		if [[ `grep $(hostname) /etc/hosts` = "" ]]
		then
			echo "Could not find /etc/hosts entry for $(hostname)."
			echo "(afs processes use the system hostname..)"
			continue
		fi
		break
	done
	cat <<__EOT

The IPv4 IP address for this afs server should resolve in dns but
definitely reside in /etc/hosts. e.g. 192.168.1.200.
__EOT
	while :; do
		ask_until "System IP?" "$(host $h | \
			awk '/has address/{print $4}')"
		ip=${resp}
		if [[ `grep $ip /etc/hosts` = "" ]]
		then
			echo "Could not find /etc/hosts entry for $ip."
			continue
		fi
		break
	done
	cat <<__EOT

The cell name is typically a dns name.  e.g. example.com.
__EOT
	ask_until "AFS Cell Name?" "${h#*.}"
	c=${resp}
	cat <<__EOT

The realm name is the KerberosV REALM, typically the capitalized dns name.
e.g. REALM.COM. Use something different at the expense of your sanity.
Really.
__EOT
	ask_until "KerberosV REALM?" "$(echo "$c"|tr "[a-z]" "[A-Z]")"
	R=${resp}
	cat <<__EOT

This is an existing KerberosV principal with the ability to
create and delete other kerberos principals.  e.g. $defuser/admin.
__EOT
	ask_until "KerberosV principal for kerberos administration?" \
		"$defuser/admin"
	p=${resp}
	echo
	ask_until "Is this the first server setup in the $c cell?" "Y"
	case "${resp}" in [Yy]*) first=1;; *) first=0;; esac
	if [ first -eq 0 ]; then
		set -A sservers fs
		echo
		ask_until "What is the primary afs server's name (e.g. afs0.example.com)?"
		psn=$resp
		ask_until "What is the primary afs server's IP (e.g. 10.1.2.3)?"
		psip=$resp

		cat <<__EOT

This principal should already exist from the 1st afs server setup, and
will be used to administer afs in a similar way that \`root'
can administrate OpenBSD.  Tread lightly when using this principal.
__EOT
	else
		set -A sservers buserver ptserver vlserver fs
		cat <<__EOT

Just a sample user to create a basic homedir and account on afs with.
__EOT
		ask_until "KerberosV principal for example user?" "$defuser"
		u=${resp}
		cat <<__EOT

This principal will be deleted if it exists, then created with specific
attributes.  It will be used to administer afs in a similar way that \`root'
can administrate OpenBSD.  Tread lightly when using this principal.
__EOT
	fi
	ask_until "KerberosV principal for afs administration?" \
		"$defuser/afs"
	A=${resp}
	slist="" i=0
	while [ i -lt ${#sservers[*]} ]; do
		slist="$slist ${sservers[$i]}"
		let i=i+1
	done
	slist="${slist# *}"
	cat <<__EOT

Each afs server can run one or more openafs server processes:
    vlserver(8) - volume location server, keeps track of volume locations
    ptserver(8) - protection server for users, groups, and permissions
    buserver(8) - permit backups to occur for vlserver and ptserver databases
             fs - fileserver, volserver, salvager (for hosts that store files)
__EOT
	ask_until "Servers to run on $h?" "$slist"
	set -A sservers $resp
else
	# for advanced users, this script can be started with the above
	# pre-populated via arguments
	h="$1" ip="$2" c="$3" R="$4" p="$5" A="$6" u="$7"
fi

slist="" i=0 vl2=0
while [ i -lt ${#sservers[*]} ]; do
	if [ "${sservers[$i]}" = "vlserver" -a first -eq 0 ]; then
		# we are a 2ndary server running vlserver, save to CellServDB
		vl2=1
	fi
	slist="$slist ${sservers[$i]}"
	let i=i+1
done
slist="${slist# *}"

pp=$(echo "$p"|sed 's,/,.,')
pA=$(echo "$A"|sed 's,/,.,')

cat <<__EOT

Confirm these look correct:
hostname   : $h
IP address : $ip
cellname   : $c
realm      : $R
krb admin  : $p (pts name: $pp)
afs admin  : $A (pts name: $pA)
afs servers: $slist
__EOT
if [ first -eq 1 ]; then
cat <<__EOT
sample user: $u
__EOT
else
cat <<__EOT
prim. name : $psn
prim. ip   : $psip
__EOT
fi
cat <<__EOT

The next step *DESTROYS* all existing OpenAFS configuration on this system,
including any openafs data on all /vicep* partitions!
__EOT

ask_yn "Are you really sure that you're ready to proceed?"
[[ $resp == n ]] && { echo "Ok, try again later.\n" ; exit ; }

echo
log "preparing ${SYSCONFDIR}/{open,}afs /usr/afs /var/openafs..."

# Initialize the filesystems
initfs() {
	kdestroy
	pkill -9 afsd
	umount /afs > /dev/null 2>&1
	if [ -d /var/spool/afs ]; then
		log clearing afs cache
		retry rm -rf -- "/var/spool/afs/*"
		retry mkdir -p /var/spool/afs
	fi
	log stopping any pre-existing arla and/or openafs daemons
	if [ "$(pgrep bosserver)" ]
	then
		bos shutdown localhost -noauth -wait > /dev/null 2>&1
	fi
	if [ "$(pgrep bosserver)" ]
	then
		bos shutdown localhost -localauth -wait > /dev/null 2>&1
	fi
	pkill -9 buserver ptserver vlserver fileserver volserver bos bosserver
	retry rm -rf ${SYSCONFDIR}/openafs /usr/afs /var/openafs
	retry rm -rf /vicep*/{V*,AFSIDat,Lock}
	retry rm -f /etc/kerberosV/krb5.keytab

	retry mkdir -p ${SYSCONFDIR}/openafs/server /usr/afs /var/spool/afs
	retry mkdir -m 700 /var/openafs
	echo "f $ip" > /var/openafs/NetInfo
	echo "$R" > ${SYSCONFDIR}/openafs/server/krb.conf
	ln -s /var/openafs/db /usr/afs/db
	ln -s ${SYSCONFDIR}/openafs/server /usr/afs/etc
}

# borrowed from /etc/security, backup CellServDB
_fnchg() {
	echo "$1" | sed 's/^\///;s/\//_/g'
}

# be extra cautious
backupcsdb() {
if [ -s $CURdb ]; then
	diff -ua $CURdb $csdb > $OUTPUT
	if [ -s $OUTPUT ]; then
		cp -p $CURdb $BACKdb
		cp -p $csdb $CURdb
		chown root:wheel $CURdb $BACKdb
	fi
else
	cp -p $csdb $CURdb
	chown root:wheel $CURdb
fi
}

# update the CellServDB's
writecsdb() {
	# put back arla's CellServDB when this script exits
	log "Adding cell $c to CellServDB"
	trap 'rm -rf $DIR; cp $CURdb $csdb; exit 1' 0 1 2 3 13 15
	echo ">$c	# $c" > $DIR/CellServDB
	if [ first -eq 0 ]; then
		echo "$psip	#$psn" >> $DIR/CellServDB
		if [ vl2 -eq 1 ]; then
			echo "$ip	#$h" >> $DIR/CellServDB
		fi
	else
		echo "$ip	#$h" >> $DIR/CellServDB
	fi
	cat $DIR/CellServDB | \
  		tee -a $csdb ${SYSCONFDIR}/openafs/server/CellServDB
}
writethiscell() {
	echo $c | tee ${SYSCONFDIR}/openafs/server/ThisCell > /etc/afs/ThisCell
}
hostkeytab() {
	kadd host/$h --random-key 
	retry kadmin ext host/$h
	chmod 0400 /etc/kerberosV/krb5.keytab
}
saveafskey() {
	log creating ${SYSCONFDIR}/openafs/server/KeyFile
	retry kadmin ext -k $DIR/afsv5key afs/$c
	log -c ktutil copy $DIR/afsv5key AFSKEYFILE:${SYSCONFDIR}/openafs/server/KeyFile
	ktutil copy $DIR/afsv5key AFSKEYFILE:${SYSCONFDIR}/openafs/server/KeyFile
	retry chmod 600 ${SYSCONFDIR}/openafs/server/KeyFile
	retry rm $DIR/afsv5key
}
createafskey() {
	kadd afs/$c --random-key 
}
addexampleuser() {
	log When asked for a password below, the answer will set it.
	let sc=sc-1
	kadd $A
}
disablearla() {
	log "Disabling arla admin commands (use openafs commands on servers):"
	retry chmod 644 /usr/sbin/{bos,pts,vos,fs}
}
setuppts() {
	log setting up pts memberships, $pA as initial afs admin
	retry pts createuser -name $u -id `id -u $u` -cell $c -noauth
	retry pts createuser -name $pA -cell $c -noauth
	retry pts adduser $pA system:administrators -cell $c -noauth
	retry pts mem system:administrators -cell $c -noauth
	retry pts listentries -cell $c -noauth
	retry bos addhost localhost $h -noauth
}
initcellp1() {
	log creating root.afs
	retry vos create $h /vicepa root.afs -noauth
	retry vos create $h /vicepa root.cell -noauth

	sync;sync
	sleep 5
	sync;sync
}
initcellp2() {
	log setting permissions/creating volumes
	retry fs sa /afs system:anyuser rl
	retry fs flushvolume /afs
	retry fs mkm /afs/.$c root.cell -cell $c -rw -fast
	retry fs flushvolume /afs/.$c
	retry fs sa  /afs/.$c system:anyuser rl
	retry fs flushvolume /afs/.$c
	cat <<__EOT> /afs/.$c/robots.txt
# noticed at ualberta.ca, attempt to prevent robots from traversing afs
User-Agent: *
Disallow: /
__EOT

	mkvol user /afs/.$c/u
	mkvol user.$defuser /afs/.$c/u/$defuser
	mkvol mirror /afs/.$c/mirror
	retry vos addsite $h /vicepa mirror
	retry vos release mirror

	log Add some remote afs cells
	# significant remote afs cells of note, and/or install mirrors
	# as a basic set of remote cells for our example root.afs
	set -A rc \
		$c \
		ualberta.ca \
		stacken.kth.se \
		grand.central.org \
		su.se \
		mrow.org
	i=0
	while [ i -lt ${#rc[*]} ]
	do
		log -c fs mkm /afs/${rc[$i]} root.cell -cell ${rc[$i]} -fast
		fs mkm /afs/${rc[$i]} root.cell -cell ${rc[$i]} -fast
		let i=i+1
	done
}

# add files of note to /etc/changelist
addchg() {
	while [ "$1" ]
	do
		f="$1"
		shift
		if egrep "^${f}$" /etc/changelist > /dev/null 2>&1; then
			continue
		fi
		echo "$f" >> /etc/changelist
	done
}

log updating /etc/afs/CellServDB ${SYSCONFDIR}/openafs/server/CellServDB

csdb=/etc/afs/CellServDB
CURdb=/var/backups/$(_fnchg $csdb).current
BACKdb=/var/backups/$(_fnchg $csdb).backup

initfs
backupcsdb
writecsdb
writethiscell
if [ "$(pgrep bosserver)" ]
then
	pkill bosserver
fi

log authenticating $p@$R
retry kinit $p@$R
hostkeytab

# only 1st afs server
if [ first -eq 1 ]; then
	createafskey
fi
saveafskey

# only 1st afs server
if [ first -eq 1 ]; then
	addexampleuser
fi
disablearla

log starting unauthenticated bosserver
retry bosserver -log -syslog -noauth
retry bos setcellname localhost $c -noauth

log creating server entries with bos
i=0
fs=0
while [ i -lt ${#sservers[*]} ]; do
	srv="${sservers[$i]}"
	case "$srv" in
	fs)
		fs=1
	;;
	*)
		retry bos create localhost $srv simple $afsp/$srv -cell $c -noauth
	;;
	esac
	let i=i+1
done

# only 1st afs server
if [ first -eq 1 ]; then
	setuppts
fi
retry bos adduser localhost $pA -cell $c -noauth
retry bos shutdown localhost -cell $c -noauth -wait

log getting status of bos config
retry bos status localhost -noauth -cell $c -long
fs=1
if [ fs -eq 1 ]; then
	log creating fs entry with bos
	retry bos create localhost fs fs $afsp/{fileserver,volserver,salvager} -cell $c -noauth
	retry bos restart localhost -all -cell $c -noauth
fi
log getting partition list
retry vos listpart localhost -noauth

# only 1st afs server
if [ first -eq 1 ]; then
	initcellp1
fi

retry bos shutdown localhost -cell $c -noauth -wait
pkill -HUP bosserver
sleep 3
# don't put back CellServDB
trap 'rm -rf $DIR; exit 1' 0 1 2 3 13 15

log starting authenticated bosserver
retry bosserver -log -syslog 
retry bos restart localhost -all -cell $c -localauth


log starting afs client
retry mkdir -p /afs
[ "$(mount | egrep "^/afs")" ] || mount -t nnpfs /dev/nnpfs0 /afs
retry /usr/libexec/afsd -z --log=/var/log/afsd.log

log authenticating $A
retry kinit $A
retry pts listentries

# do this on reboot
grep "^afs=YES" /etc/rc.conf > /dev/null 2>&1 || \
grep "^afs=YES" /etc/rc.conf.local > /dev/null 2>&1 || \
echo afs=YES >> /etc/rc.conf.local

retry ls /afs

# only 1st afs server
if [ first -eq 1 ]; then
	initcellp2
fi

log adding replication sites for root.afs, root.cell
retry vos addsite $h /vicepa root.afs
retry vos addsite $h /vicepa root.cell

log release of replicated volumes
retry vos release root.afs
retry vos release root.cell

log restart afsd so it sees RO replicated volumes
pkill afsd
retry /usr/libexec/afsd -z --log=/var/log/afsd.log

addchg /etc/kerberosV/krb5.conf
addchg "+/etc/kerberosV/krb5.keytab"
addchg /etc/afs/{CellServDB,ThisCell,afsd.conf}
addchg ${SYSCONFDIR}/openafs/{BosConfig,server/{CellServDB,ThisCell,UserList,krb.conf}}
addchg "+${SYSCONFDIR}/openafs/server/KeyFile"
addchg /var/openafs/NetInfo
addchg "+/var/openafs/sysid"
addchg "+/var/openafs/db/bdb.DB0"
addchg "+/var/openafs/db/bdb.DBSYS1"
addchg "+/var/openafs/db/prdb.DB0"
addchg "+/var/openafs/db/prdb.DBSYS1"
addchg "+/var/openafs/db/vldb.DB0"
addchg "+/var/openafs/db/vldb.DBSYS1"

# Pat on the back.
cat <<__EOT

CONGRATULATIONS! Your OpenAFS server setup has been successfully completed,
and is now running.
Please read ${TRUEPREFIX}/share/openafs/README.OpenBSD for further details;
be sure to note the startup and shutdown script examples.
__EOT
